package com.fount4j.generator.context;

import com.fount4j.generator.config.*;
import com.fount4j.generator.exception.GenerateException;
import com.fount4j.generator.introspect.*;
import com.fount4j.generator.jdbc.JdbcConfigConnectionFactory;
import com.fount4j.generator.util.BeanUtils;
import com.fount4j.generator.util.StringUtils;
import org.beetl.core.GroupTemplate;
import org.beetl.core.ResourceLoader;
import org.beetl.core.resource.ClasspathResourceLoader;
import org.beetl.core.resource.FileResourceLoader;
import org.beetl.core.resource.WebAppResourceLoader;
import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * YAML 配置文件上下文
 *
 * @author Morven 2016/12/22
 */
public class YmlGeneratorContext extends GeneratorContext {

    public YmlGeneratorContext(String configPath) {
        if (configPath.startsWith("file:")) {
            try {
                loadConfiguration(new FileInputStream(configPath.replaceFirst("file:", "")));
            } catch (FileNotFoundException e) {
                throw new GenerateException("找不到配置文件", e);
            }
        } else {
            loadConfiguration(YmlGeneratorContext.class.getResourceAsStream(configPath));
        }
    }

    public YmlGeneratorContext(File configFile) {
        try {
            loadConfiguration(new FileInputStream(configFile));
        } catch (FileNotFoundException e) {
            throw new GenerateException("找不到配置文件", e);
        }
    }

    public YmlGeneratorContext(InputStream configInputStream) {
        loadConfiguration(configInputStream);
    }

    protected void loadConfiguration(InputStream is) {
        Yaml yaml = new Yaml();
        Map<String, Object> configMap = (Map<String, Object>) yaml.load(is);
        parseClassLoader(configMap);
        parseJdbc(configMap);
        parseEntity(configMap);
        parseDao(configMap);
        parseMapper(configMap);
        parseTemplate(configMap);
        parseTables(configMap);
        parseIntrospector(configMap);
        properties = configMap;
    }

    /**
     * 加载 ClassLoader
     *
     * @param configMap 配置文件对象
     */
    protected void parseClassLoader(Map<String, Object> configMap) {
        List<URL> urls = new ArrayList<URL>();
        File file;
        List<String> entries = (List<String>) configMap.remove("classPathEntry");
        if (entries != null) {
            for (String classPathEntry : entries) {
                file = new File(classPathEntry);
                if (!file.exists()) {
                    throw new GenerateException("找不到文件 classPathEntry: " + classPathEntry);
                }
                try {
                    urls.add(file.toURI().toURL());
                } catch (MalformedURLException e) {
                    throw new GenerateException("解析 classPathEntry 出错", e);
                }
            }
        }
        ClassLoader parent = Thread.currentThread().getContextClassLoader();
        classLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), parent);
    }

    /**
     * 通过配置文件加载解析器
     *
     * @param configMap            配置文件对象
     * @param configKey            配置键值
     * @param defIntrospectorClass 默认的解析器类
     * @return 解析器对象
     */
    protected BaseIntrospector parseBaseIntrospector(Map<String, Object> configMap, String configKey,
            Class<? extends BaseIntrospector> defIntrospectorClass) {
        String introspectorConfig = (String) configMap.remove(configKey);
        BaseIntrospector introspector;
        try {
            if (StringUtils.isNotBlank(introspectorConfig)) {
                introspector = (BaseIntrospector) BeanUtils.newInstance(introspectorConfig);
            } else {
                introspector = BeanUtils.newInstance(defIntrospectorClass);
            }
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            throw new GenerateException("初始化解析器出错: " + defIntrospectorClass, e);
        }
        return introspector;
    }

    /**
     * 解析器配置
     *
     * @param configMap 要解析的配置
     */
    protected void parseIntrospector(Map<String, Object> configMap) {
        String introspectorConfig = (String) configMap.remove("introspector");
        if (StringUtils.isNotBlank(introspectorConfig)) {
            try {
                introspector = (Introspector) BeanUtils.newInstance(introspectorConfig);
            } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                throw new GenerateException("初始化解析器出错: " + introspectorConfig, e);
            }
        } else {
            introspector = new Introspector();
        }

        String columnIntrospectorConfig = (String) configMap.remove("columnIntrospector");
        if (StringUtils.isNotBlank(columnIntrospectorConfig)) {
            try {
                columnIntrospector = (ColumnIntrospector) BeanUtils.newInstance(columnIntrospectorConfig);
            } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                throw new GenerateException("初始化解析器出错: " + columnIntrospectorConfig, e);
            }
        } else {
            columnIntrospector = new ColumnIntrospector();
        }

        entityIntrospector = parseBaseIntrospector(configMap, "entityIntrospector", EntityIntrospector.class);
        daoIntrospector = parseBaseIntrospector(configMap, "daoIntrospector", DaoIntrospector.class);
        mapperIntrospector = parseBaseIntrospector(configMap, "mapperIntrospector", MapperIntrospector.class);
    }

    /**
     * 解析要生成的表
     *
     * @param configMap 要解析的配置
     */
    protected void parseTables(Map<String, Object> configMap) {
        tables = parseConfigBeanList(TableConfig.class, (List<Map<String, Object>>) configMap.remove("tables"));
    }

    /**
     * 创建模板引擎加载器
     *
     * @param templateMap 配置项
     * @return 模板引擎加载器
     */
    protected ResourceLoader createResourceLoader(Map<String, Object> templateMap) {
        ResourceLoader resourceLoader;
        String root = (String) templateMap.remove("root");
        if (StringUtils.isNotBlank(root)) {
            if (root.startsWith("file:")) {
                resourceLoader = new FileResourceLoader(root.replaceFirst("file:", ""));
            } else if (root.startsWith("webapp:")) {
                resourceLoader = new WebAppResourceLoader(root.replaceFirst("webapp:", ""));
            } else {
                resourceLoader = new ClasspathResourceLoader(root);
            }
        } else {
            resourceLoader = new ClasspathResourceLoader();
        }
        return resourceLoader;
    }

    /**
     * 解析模板配置
     *
     * @param configMap 要解析的配置
     */
    protected void parseTemplate(Map<String, Object> configMap) {
        try {
            Map<String, Object> templateMap = (Map<String, Object>) configMap.remove("template");
            if (templateMap != null && !templateMap.isEmpty()) {
                ResourceLoader resourceLoader = createResourceLoader(templateMap);

                org.beetl.core.Configuration cfg;
                String config = (String) templateMap.remove("config");
                if (StringUtils.isNotBlank(config)) {
                    cfg = new org.beetl.core.Configuration();
                    if (config.startsWith("file:")) {
                        cfg.add(new File(config.replaceFirst("file:", "")));
                    } else {
                        cfg.add(config);
                    }
                } else {
                    cfg = org.beetl.core.Configuration.defaultConfiguration();
                }

                GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);

                String entityTemplateConfig = (String) templateMap.remove("entityTemplate");
                String daoTemplateConfig = (String) templateMap.remove("daoTemplate");
                String mapperTemplateConfig = (String) templateMap.remove("mapperTemplate");

                if (StringUtils.isNotBlank(entityTemplateConfig)) {
                    entityTemplate = gt.getTemplate(entityTemplateConfig);
                } else {
                    entityTemplate = gt.getTemplate("entity.btl");
                }
                if (StringUtils.isNotBlank(daoTemplateConfig)) {
                    daoTemplate = gt.getTemplate(daoTemplateConfig);
                } else {
                    daoTemplate = gt.getTemplate("dao.btl");
                }
                if (StringUtils.isNotBlank(mapperTemplateConfig)) {
                    mapperTemplate = gt.getTemplate(mapperTemplateConfig);
                } else {
                    mapperTemplate = gt.getTemplate("mapper.btl");
                }
            } else {
                ClasspathResourceLoader resourceLoader = new ClasspathResourceLoader("com/fount4j/generator/template/");
                org.beetl.core.Configuration cfg = org.beetl.core.Configuration.defaultConfiguration();
                GroupTemplate gt = new GroupTemplate(resourceLoader, cfg);
                entityTemplate = gt.getTemplate("entity.btl");
                daoTemplate = gt.getTemplate("dao.btl");
                mapperTemplate = gt.getTemplate("mapper.btl");
            }
        } catch (IOException e) {
            throw new GenerateException("加载文件模板出错", e);
        }
    }

    /**
     * 解析映射文件配置
     *
     * @param configMap 要解析的配置
     */
    private void parseMapper(Map<String, Object> configMap) {
        mapperConfig = parseConfigBean(MapperConfig.class, (Map<String, Object>) configMap.remove("mapper"));
    }

    /**
     * 解析 Dao 类配置
     *
     * @param configMap 要解析的配置
     */
    private void parseDao(Map<String, Object> configMap) {
        daoConfig = parseConfigBean(DaoConfig.class, (Map<String, Object>) configMap.remove("dao"));
    }

    /**
     * 解析实体类配置
     *
     * @param configMap 要解析的配置
     */
    private void parseEntity(Map<String, Object> configMap) {
        entityConfig = parseConfigBean(EntityConfig.class, (Map<String, Object>) configMap.remove("entity"));
    }

    /**
     * 解析 JDBC 配置
     *
     * @param configMap 要解析的配置
     */
    protected void parseJdbc(Map<String, Object> configMap) {
        JdbcConfig jdbcConfig = parseConfigBean(JdbcConfig.class, (Map<String, Object>) configMap.remove("jdbc"));
        connectionFactory = new JdbcConfigConnectionFactory(jdbcConfig, classLoader);
    }

    /**
     * 将 List 类型的配置转为配置项集合
     *
     * @param configBeanClass 配置项类型
     * @param configList      配置集合
     * @param <T>             泛型
     * @return 配置项实体类的集合
     */
    protected <T> List<T> parseConfigBeanList(Class<T> configBeanClass, List<Map<String, Object>> configList) {
        if (configList == null || configList.isEmpty()) {
            return null;
        }
        List<T> list = new ArrayList<>();
        for (Map<String, Object> item : configList) {
            list.add(parseConfigBean(configBeanClass, item));
        }
        return list;
    }

    /**
     * 根据字段获取配置项的值
     *
     * @param field     字段
     * @param configMap 配置项
     * @return 配置项的值
     */
    protected Object getConfigValue(Field field, Map<String, Object> configMap) {
        String name = field.getName();
        Object objVal = configMap.remove(name);
        if (objVal == null) {
            return null;
        }
        Class<?> fieldType = field.getType();
        if (!fieldType.equals(objVal.getClass())) {
            if (fieldType.equals(Long.class)) {
                objVal = Long.parseLong(objVal.toString());
            } else if (fieldType.equals(Integer.class)) {
                objVal = Integer.parseInt(objVal.toString());
            } else if (fieldType.equals(Boolean.class)) {
                objVal = Boolean.parseBoolean(objVal.toString());
            } else if (fieldType.equals(Float.class)) {
                objVal = Float.parseFloat(objVal.toString());
            } else if (fieldType.equals(Double.class)) {
                objVal = Double.parseDouble(objVal.toString());
            } else if (fieldType.equals(List.class)) {
                objVal = (List) objVal;
            } else {
                throw new GenerateException("无法解析的配置项类型：" + name);
            }
        }
        return objVal;
    }

    /**
     * 将 Map 类型的配置值转为配置实体类对象
     *
     * @param configBeanClass 配置实体类型
     * @param configMap       配置项
     * @param <T>             泛型
     * @return 实体类对象
     * @throws GenerateException 异常
     */
    protected <T> T parseConfigBean(Class<T> configBeanClass, Map<String, Object> configMap) {
        T newInstance;
        try {
            newInstance = configBeanClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new GenerateException("创建配置类对象出错", e);
        }
        if (configMap == null) {
            return newInstance;
        }

        Field[] fields = configBeanClass.getDeclaredFields();
        for (Field field : fields) {
            String name = field.getName();
            Object objVal = getConfigValue(field, configMap);
            if (objVal != null) {
                try {
                    BeanUtils.setProperty(newInstance, name, objVal);
                } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                    throw new GenerateException("加载配置值出错: " + configBeanClass + "." + name, e);
                }
            }
        }
        if (newInstance instanceof PropertyHolder) {
            parseProperties((PropertyHolder) newInstance, configMap);
        }
        return newInstance;
    }

    /**
     * 解析其他参数
     *
     * @param propertyHolder 各类配置实体
     * @param configMap      从文件中读取到的配置
     */
    protected void parseProperties(PropertyHolder propertyHolder, Map<String, Object> configMap) {
        propertyHolder.setProperties(configMap);
    }
}
